

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
/* see: http://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html */
/* type of msg: request from nginx to servlet */

#define NGX_AJP_PACKET_SIZE       8192 /* 8 * 1024 */
#define NGX_AJP_DATA_SIZE         8188
#define NGX_AJP_HEADER_SIZE          4

#define NGX_AJP_LENGTH_POS           2
#define NGX_AJP_TYPE_POS             4
#define NGX_AJP_RESPONSE_DATA_POS    5
#define NGX_AJP_CHUNK_DATA_POS       7

#define AJP_PACKET_FORWARD_REQUEST  2
#define AJP_PACKET_SHUTDOWN         7
#define AJP_PACKET_PING             8
#define AJP_PACKET_CPING           10

/* type of msg: response from servlet to nginx */
#define AJP_PACKET_SEND_BODY_CHUNK  3
#define AJP_PACKET_SEND_HEADERS     4
#define AJP_PACKET_END_RESPONSE     5
#define AJP_PACKET_GET_BODY_CHUNK   6
#define AJP_PACKET_CPONG_REPLY      9

/* http method encoding */
#define AJP_METHOD_UNKNOWN       0xFF
#define AJP_METHOD_PATCH         AJP_METHOD_UNKNOWN
#define AJP_METHOD_OPTIONS        1
#define AJP_METHOD_GET            2
#define AJP_METHOD_HEAD           3
#define AJP_METHOD_POST           4
#define AJP_METHOD_PUT            5
#define AJP_METHOD_DELETE         6
#define AJP_METHOD_TRACE          7
#define AJP_METHOD_PROPFIND       8
#define AJP_METHOD_PROPPATCH      9
#define AJP_METHOD_MKCOL         10
#define AJP_METHOD_COPY          11
#define AJP_METHOD_MOVE          12
#define AJP_METHOD_LOCK          13
#define AJP_METHOD_UNLOCK        14
#define AJP_METHOD_ACL           15
#define AJP_METHOD_REPORT        16
#define AJP_METHOD_VERSION_CONTROL  17
#define AJP_METHOD_CHECKIN       18
#define AJP_METHOD_CHECKOUT      19
#define AJP_METHOD_UNCHECKOUT    20
#define AJP_METHOD_SEARCH        21
#define AJP_METHOD_MKWORKSPACE   22
#define AJP_METHOD_UPDATE        23
#define AJP_METHOD_LABEL         24
#define AJP_METHOD_MERGE         25
#define AJP_METHOD_BASELINE_CONTROL 26
#define AJP_METHOD_MKACTIVITY    27

/* http header encoding */
#define AJP_REQUEST_HEADER_UNCOMMON 0xFFFF
#define AJP_REQUEST_ACCEPT          0xA001
#define AJP_REQUEST_ACCEPT_CHARSET  0xA002
#define AJP_REQUEST_ACCEPT_ENCODING 0xA003
#define AJP_REQUEST_ACCEPT_LANGUAGE 0xA004
#define AJP_REQUEST_AUTHORIZATION   0xA005
#define AJP_REQUEST_CONNECTION      0xA006
#define AJP_REQUEST_CONTENT_TYPE    0xA007
#define AJP_REQUEST_CONTENT_LENGTH  0xA008
#define AJP_REQUEST_COOKIE          0xA009
#define AJP_REQUEST_COOKIE2         0xA00A
#define AJP_REQUEST_HOST            0xA00B
#define AJP_REQUEST_PRAGMA          0xA00C
#define AJP_REQUEST_REFERER         0xA00D
#define AJP_REQUEST_USER_AGENT      0xA00E

/* Attributes */
#define AJP_ATTR_CONTEXT            1
#define AJP_ATTR_SERVLET_PATH       2
#define AJP_ATTR_REMOTE_USER        3
#define AJP_ATTR_AUTH_TYPE          4
#define AJP_ATTR_QUERY_STRING       5
#define AJP_ATTR_JVM_ROUTE          6
#define AJP_ATTR_SSL_CERT           7
#define AJP_ATTR_SSL_CIPHER         8
#define AJP_ATTR_SSL_SESSION        9
#define AJP_ATTR_REQ_ATTRIBUTE     10
#define AJP_ATTR_SSL_KEY_SIZE      11
#define AJP_ATTR_SECRET            12
#define AJP_ATTR_ARE_DONE          0xFF /* request_terminator */

#define AJP_REQUEST_TERMINATOR     AJP_ATTR_ARE_DONE


#define AJP_RESPONSE_CONTENT_TYPE     0xA001
#define AJP_RESPONSE_CONTENT_LANGUAGE 0xA002
#define AJP_RESPONSE_CONTENT_LENGTH   0xA003
#define AJP_RESPONSE_DATE             0xA004
#define AJP_RESPONSE_LAST_MODIFIED    0xA005
#define AJP_RESPONSE_LOCATION         0xA006
#define AJP_RESPONSE_SET_COOKIE       0xA007
#define AJP_RESPONSE_SET_COOKIE2      0xA008
#define AJP_RESPONSE_SERVLET_ENGINE   0xA009
#define AJP_RESPONSE_STATUS           0xA00A
#define AJP_RESPONSE_WWW_AUTHENTICATE 0xA00B


/* header (4) + type(1) + reuse(1) */
#define NGX_HTTP_AJP_END   (NGX_AJP_HEADER_SIZE + 2)


typedef struct {
    ngx_http_upstream_conf_t   upstream;
    size_t                     max_packet_size;
    ngx_str_t                  location;

    ngx_array_t               *flushes;
    ngx_array_t               *params_len;
    ngx_array_t               *params;
    ngx_array_t               *params_source;
    ngx_hash_t                 headers_hash;
    ngx_uint_t                 header_params;

    ngx_array_t               *ajp_lengths;
    ngx_array_t               *ajp_values;

    ngx_str_t                  uri;

#if (NGX_HTTP_CACHE)
    ngx_http_complex_value_t       cache_key;
#endif

} ngx_http_ajp_loc_conf_t;


typedef struct {
    ngx_http_request_t        *request;

    ngx_buf_t                 *buf;

    /* last sent request type */
    unsigned                   request_type:8;

    unsigned                   send_packet:1;

    /* upstream read ready */
    unsigned                   read_ready:1;

    /* packet to send */
    ngx_chain_t               *packet;

    /* rest request body */
    off_t                      rest;
    ngx_chain_t               *body_rest;

    ngx_http_upstream_handler_pt   read_event_handler;
    ngx_http_upstream_handler_pt   write_event_handler;
} ngx_http_ajp_ctx_t;

static ngx_str_t  ngx_http_ajp_hide_headers[] = {
    ngx_string("Date"),
    ngx_string("Server"),
    ngx_string("X-Pad"),
    ngx_string("X-Accel-Expires"),
    ngx_string("X-Accel-Redirect"),
    ngx_string("X-Accel-Limit-Rate"),
    ngx_string("X-Accel-Buffering"),
    ngx_string("X-Accel-Charset"),
    ngx_null_string
};

static const u_char ngx_http_ajp_empty_packet[] = {0x12, 0x34, 0x00, 0x00};

static void *ngx_http_ajp_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_ajp_merge_loc_conf(ngx_conf_t *cf,
        void *parent, void *child);
static ngx_int_t
ngx_http_ajp_merge_params(ngx_conf_t *cf, ngx_http_ajp_loc_conf_t *conf,
                          ngx_http_ajp_loc_conf_t *prev);

static char *ngx_http_ajp_store(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

static char *ngx_http_ajp_pass(ngx_conf_t *cf, ngx_command_t *cmd,
                               void *conf);
static void
ngx_http_ajp_send_request_packet(ngx_http_ajp_ctx_t *ctx, ngx_chain_t *packet);
static ngx_int_t
ngx_http_ajp_create_request(ngx_http_request_t *r);
static ngx_int_t
ngx_http_ajp_reinit_request(ngx_http_request_t *r);
static ngx_int_t
ngx_http_ajp_process_header(ngx_http_request_t *r);
static void
ngx_http_ajp_abort_request(ngx_http_request_t *r);
static void
ngx_http_ajp_finalize_request(ngx_http_request_t *r, ngx_int_t rc);
static ngx_int_t
ngx_http_ajp_filter_init(void *data);
static ngx_int_t
ngx_http_ajp_filter(void *data, ssize_t bytes);

static ngx_int_t
ngx_http_ajp_eval(ngx_http_request_t *r, ngx_http_ajp_loc_conf_t *alcf);

static ngx_int_t
ngx_http_ajp_process_response(ngx_http_ajp_ctx_t *ctx);
static ngx_int_t
ngx_http_ajp_init_data(ngx_chain_t **chain, ngx_http_request_t *r);

static void
ngx_http_ajp_send_request_packet_handler(ngx_http_request_t *r, ngx_http_upstream_t *u);

static void
ngx_http_upstream_finalize_request(ngx_http_request_t *r,
                                   ngx_http_upstream_t *u, ngx_int_t rc);

static ngx_conf_bitmask_t ngx_http_ajp_next_upstream_masks[] = {
    { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR },
    { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT },
    { ngx_string("invalid_header"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER },
    { ngx_string("http_500"), NGX_HTTP_UPSTREAM_FT_HTTP_500 },
    { ngx_string("http_503"), NGX_HTTP_UPSTREAM_FT_HTTP_503 },
    { ngx_string("http_404"), NGX_HTTP_UPSTREAM_FT_HTTP_404 },
    { ngx_string("updating"), NGX_HTTP_UPSTREAM_FT_UPDATING },
    { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF },
    { ngx_null_string, 0 }
};


static ngx_command_t  ngx_http_ajp_commands[] = {

    {
        ngx_string("ajp_pass"),
        NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
        ngx_http_ajp_pass,
        NGX_HTTP_LOC_CONF_OFFSET,
        0,
        NULL
    },

    {
        ngx_string("ajp_bind"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_http_upstream_bind_set_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_ajp_loc_conf_t, upstream.local),
        NULL
    },

    {
        ngx_string("ajp_connect_timeout"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_conf_set_msec_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_ajp_loc_conf_t, upstream.connect_timeout),
        NULL
    },

    {
        ngx_string("ajp_send_timeout"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_conf_set_msec_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_ajp_loc_conf_t, upstream.send_timeout),
        NULL
    },

    {
        ngx_string("ajp_max_packet_size"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_conf_set_size_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_ajp_loc_conf_t, max_packet_size),
        NULL
    },

    /*
        {
            ngx_string("ajp_buffer_size"),
            NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
            ngx_conf_set_size_slot,
            NGX_HTTP_LOC_CONF_OFFSET,
            offsetof(ngx_http_ajp_loc_conf_t, upstream.buffer_size),
            NULL
        },
    */

    {
        ngx_string("proxy_pass_request_body"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
        ngx_conf_set_flag_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_ajp_loc_conf_t, upstream.pass_request_body),
        NULL
    },

    {
        ngx_string("ajp_read_timeout"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_conf_set_msec_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_ajp_loc_conf_t, upstream.read_timeout),
        NULL
    },

    {
        ngx_string("ajp_store"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_http_ajp_store,
        NGX_HTTP_LOC_CONF_OFFSET,
        0,
        NULL
    },

    {
        ngx_string("ajp_param"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE23,
        ngx_conf_set_keyval_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_ajp_loc_conf_t, params_source),
        NULL
    },


    {
        ngx_string("ajp_next_upstream"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
        ngx_conf_set_bitmask_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_ajp_loc_conf_t, upstream.next_upstream),
        &ngx_http_ajp_next_upstream_masks
    },

    ngx_null_command
};


static ngx_http_module_t  ngx_http_ajp_module_ctx = {
    NULL,                                  /* preconfiguration */
    NULL,                                  /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    ngx_http_ajp_create_loc_conf,          /* create location configration */
    ngx_http_ajp_merge_loc_conf            /* merge location configration */
};


ngx_module_t  ngx_http_ajp_module = {
    NGX_MODULE_V1,
    &ngx_http_ajp_module_ctx,              /* module context */
    ngx_http_ajp_commands,                 /* module directives */
    NGX_HTTP_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};

static inline u_char 
ngx_http_ajp_get_method(ngx_http_request_t *r)
{

    /* http method encoding */
    switch (r->method) {
    case NGX_HTTP_GET:
        return AJP_METHOD_GET;

    case NGX_HTTP_HEAD:
        return AJP_METHOD_HEAD;

    case NGX_HTTP_POST:
        return AJP_METHOD_POST;

    case NGX_HTTP_PUT:
        return AJP_METHOD_PUT;

    case NGX_HTTP_DELETE:
        return AJP_METHOD_DELETE;

    case NGX_HTTP_MKCOL:
        return AJP_METHOD_MKCOL;

    case NGX_HTTP_COPY:
        return AJP_METHOD_COPY;

    case NGX_HTTP_MOVE:
        return AJP_METHOD_MOVE;

    case NGX_HTTP_OPTIONS:
        return AJP_METHOD_OPTIONS;

    case NGX_HTTP_PROPFIND:
        return AJP_METHOD_PROPFIND;

    case NGX_HTTP_PROPPATCH:
        return AJP_METHOD_PROPPATCH;

    case NGX_HTTP_LOCK:
        return AJP_METHOD_LOCK;

    case NGX_HTTP_UNLOCK:
        return AJP_METHOD_UNLOCK;

    case NGX_HTTP_PATCH:
        return AJP_METHOD_PATCH;

    case NGX_HTTP_TRACE:
        return AJP_METHOD_TRACE;

    default:
		// TODO: parse unknown method
        /* parse unknown method
         * REPORT
         * ACL
         * VERSION_CONTROL
         * CHECKIN
         * CHECKOUT
         * UNCHECKOUT
         * SEARCH
         * MKWORKSPACE
         * UPDATE
         * LABEL
         * MERGE
         * BASELINE_CONTROL
         * MKACTIVITY
         */
        return AJP_METHOD_UNKNOWN;
    }

}

static inline ngx_uint_t ngx_http_ajp_common_header(ngx_table_elt_t *header)
{
    u_char             *p;
    size_t              len;

    if (header->key.len < 4 || header->key.len > 15) {
        return AJP_REQUEST_HEADER_UNCOMMON;
    }

    p = header->lowcase_key;
    len = header->key.len;

    switch (p[0]) {
    case 'a':
        if ( len == 6 ) {
            if (ngx_strncmp(&p[1], "ccept", 5) == 0) {
                return AJP_REQUEST_ACCEPT;
            }
        } else if ( len == 14) {
            if (ngx_strncmp(&p[1], "ccept-charset", 13) == 0) {
                return AJP_REQUEST_ACCEPT_CHARSET;
            }
        } else if ( len == 15) {
            if (ngx_strncmp(&p[1], "ccept-language", 14) == 0) {
                return AJP_REQUEST_ACCEPT_LANGUAGE;
            } else if (ngx_strncmp(&p[1], "ccept-encoding", 14) == 0) {
                return AJP_REQUEST_ACCEPT_ENCODING;
            }
        } else if (len == 13) {
            if (ngx_strncmp(&p[1], "uthorization", 12) == 0) {
                return AJP_REQUEST_AUTHORIZATION;
            }
        }

        break;

    case 'c':

        if ( len == 6) {
            if (ngx_strncmp(&p[1], "ookie", 5) == 0) {
                return AJP_REQUEST_COOKIE;
            }
        } else if ( len == 7 ) {
            if (ngx_strncmp(&p[1], "okie2", 6) == 0) {
                return AJP_REQUEST_COOKIE2;
            }
        } else if ( len == 10) {
            if (ngx_strncmp(&p[1], "onnection", 9) == 0) {
                return AJP_REQUEST_CONNECTION;
            }
        } else if ( len == 12) {
            if (ngx_strncmp(&p[1], "ontent-type", 11) == 0) {
                return AJP_REQUEST_CONTENT_TYPE;
            }
        } else if ( len == 14) {
            if (ngx_strncmp(&p[1], "ontent-length", 13) == 0) {
                return AJP_REQUEST_CONTENT_LENGTH;
            }
        }

        break;

    case 'h':
        if (len == 4 && ngx_strncmp(&p[1], "ost", 3) == 0) {
            return AJP_REQUEST_HOST;
        }

        break;

    case 'p':
        if (len == 6 && ngx_strncmp(&p[1],"ragma", 5) == 0) {
            return AJP_REQUEST_PRAGMA;
        }

        break;

    case 'r':
        if (len == 7 && ngx_strncmp(&p[1], "eferer", 6) == 0) {
            return AJP_REQUEST_REFERER;
        }

        break;

    case 'u':
        if (len == 10 && ngx_strncmp(&p[1], "ser-agent", 9) == 0) {
            return AJP_REQUEST_USER_AGENT;
        }

        break;

    default:
        break;
    }

    return AJP_REQUEST_HEADER_UNCOMMON;
}


static inline ngx_uint_t
ngx_list_size(ngx_list_t *list)
{
    ngx_list_part_t *part;
    ngx_uint_t       size;

    size = 0;
    part = &list->part;

    while (part) {
        size += part->nelts;
        part = part->next;
    }

    return size;
}


static inline void
ngx_buf_trim(ngx_buf_t *buf)
{
    if (buf->pos != buf->start) {
        ngx_memmove(buf->start, buf->pos, buf->last - buf->pos);
        buf->last += buf->start - buf->pos;
        buf->pos = buf->start;
    }
}


static inline ngx_uint_t
ngx_http_ajp_2char_integer(u_char *p)
{
    return (p[0] << 8) + p[1];
}


static ngx_int_t
ngx_http_ajp_request_init(ngx_buf_t *buf)
{
    /* assume that buf size is bigger enough */
    buf->last[0] = 0x12;
    buf->last[1] = 0x34;
    /* slot for data length */
    buf->last += 4;

    return NGX_OK;
}


static ngx_int_t
ngx_http_ajp_request_completion(ngx_buf_t *buf)
{

    ngx_uint_t     datalen;

    datalen = buf->last - buf->start - 4;
    buf->start[2] = (u_char) ((datalen >> 8) & 0xFF);
    buf->start[3] = (u_char) (datalen & 0xFF);

    return NGX_OK;
}


static ngx_int_t
ngx_http_ajp_request_append_byte(ngx_buf_t *buf, u_char c)
{
    if (buf->last + 1 > buf->end) {
        return NGX_ERROR;
    }

    *buf->last++ = c;
    return NGX_OK;
}



static ngx_int_t
ngx_http_ajp_request_append_bool(ngx_buf_t *buf, ngx_uint_t c)
{
    return ngx_http_ajp_request_append_byte(buf, (c != 0));
}


/* 2 bytes little endian */
static ngx_int_t
ngx_http_ajp_request_append_integer(ngx_buf_t *buf, ngx_uint_t i)
{
    if (buf->last + 2 > buf->end) {
        return NGX_ERROR;
    }

    *buf->last++ = (u_char) ((i >> 8) & 0xFF);
    *buf->last++ = (u_char) (i & 0xFF);

    return NGX_OK;
}

/*
static ngx_int_t
ngx_http_ajp_request_append_bytes(ngx_buf_t *buf, u_char *bytes, ngx_uint_t size)
{
    if (buf->last + size > buf->end) {
        return NGX_ERROR;
    }

    ngx_memcpy(buf->last, bytes, size);
    buf->last += size;
    return NGX_OK;
}
*/

static ngx_int_t
ngx_http_ajp_request_append_terminator(ngx_buf_t *buf)
{
    return ngx_http_ajp_request_append_byte(buf, AJP_REQUEST_TERMINATOR);
}

/*
A variable-sized string (length bounded by 2^16).
Encoded with the length packed into two bytes first,
followed by the string (including the terminating '\0').
Note that the encoded length does not include the trailing '\0' -- it is like strlen.
Note a size of -1 (65535) indicates a null string and no data follow the length in this case.
We don't use -1.
*/

static ngx_int_t
ngx_http_ajp_request_append_string(ngx_buf_t *buf, ngx_str_t *str)
{
    if (buf->last + str->len + 3 > buf->end) {
        return NGX_ERROR;
    }

    *buf->last++ = (u_char) ((str->len >> 8) & 0xFF);
    *buf->last++ = (u_char) (str->len & 0xFF);

    ngx_memcpy(buf->last, str->data, str->len);
    buf->last += str->len;
    *buf->last++ = '\0';
    return NGX_OK;
}



static ngx_int_t
ngx_http_ajp_request_append_header(ngx_buf_t *buf, ngx_table_elt_t *header)
{
    ngx_uint_t        code;

    if ( (code = ngx_http_ajp_common_header(header) ) == AJP_REQUEST_HEADER_UNCOMMON ) {
        if (ngx_http_ajp_request_append_string(buf, &header->key) != NGX_OK
                || ngx_http_ajp_request_append_string(buf, &header->value) != NGX_OK) {
            return NGX_ERROR;
        }

    } else {
        if (ngx_http_ajp_request_append_integer(buf, code) != NGX_OK
                || ngx_http_ajp_request_append_string(buf, &header->value) != NGX_OK) {
            return NGX_ERROR;
        }
    }

    return NGX_OK;

}

static ngx_int_t
ngx_http_ajp_request_append_headers(ngx_buf_t *buf, ngx_list_t *headers)
{
    ngx_uint_t            i;
    ngx_list_part_t      *part;
    ngx_table_elt_t      *header;


    ngx_http_ajp_request_append_integer(buf, ngx_list_size(headers));
    /* request_headers *(req_header_name req_header_value) */
    part = &headers->part;
    header = part->elts;

    for (i = 0;; i++) {
        if (i >= part->nelts) {
            if (part->next == NULL) {
                break;
            }

            part = part->next;
            header = part->elts;
            i = 0;
        }

        if ( ngx_http_ajp_request_append_header(buf, &header[i]) != NGX_OK) {
            return NGX_ERROR;
        }
    }

    return NGX_OK;
}


static ngx_int_t
ngx_http_ajp_request_append_attributes(ngx_buf_t *buf, ngx_http_request_t *r)
{
    // TODO:

    return NGX_OK;
}


/*
static ngx_int_t
ngx_http_ajp_buf_init_shutdown(ngx_buf_t *buf)
{
    if (ngx_http_ajp_request_init(buf) != NGX_OK
            || ngx_http_ajp_request_append_byte(buf, AJP_PACKET_SHUTDOWN) != NGX_OK
            || ngx_http_ajp_request_completion(buf) != NGX_OK) {
        return NGX_ERROR;
    }

    return NGX_OK;
}
*/

static ngx_int_t
ngx_http_ajp_request_append_uri(ngx_buf_t *buf, ngx_http_request_t *r)
{
    ngx_http_ajp_loc_conf_t    *alcf;
    size_t                      uri_len, loc_len;
    ngx_uint_t                  unparsed_uri;
    uintptr_t                   escape;

    alcf = ngx_http_get_module_loc_conf(r, ngx_http_ajp_module);

    if (alcf->ajp_lengths && alcf->uri.len) {
        uri_len = alcf->uri.len;

    } else if (alcf->uri.len == 0 && r->valid_unparsed_uri && r == r->main) {
        unparsed_uri = 1;
        uri_len = r->unparsed_uri.len;

    } else {
        loc_len = (r->valid_location && alcf->uri.len) ?
                  alcf->location.len : 0;

        if (r->quoted_uri || r->space_in_uri || r->internal) {
            escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len,
                                        r->uri.len - loc_len, NGX_ESCAPE_URI);
        }

        uri_len = alcf->uri.len + r->uri.len - loc_len + escape
                  + sizeof("?") - 1 + r->args.len;
    }

    if (uri_len == 0) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "zero length URI to ajp");
        return NGX_ERROR;
    }

    if (uri_len + 3 + buf->last > buf->end) {
        return NGX_ERROR;
    }

    if (alcf->ajp_lengths && alcf->uri.len) {
        if (ngx_http_ajp_request_append_string(buf, &alcf->uri) != NGX_OK) {
            return NGX_ERROR;
        }

    } else if (unparsed_uri) {
        if (ngx_http_ajp_request_append_string(buf, &r->unparsed_uri) != NGX_OK) {
            return NGX_ERROR;
        }

    } else {
        if (ngx_http_ajp_request_append_integer(buf, uri_len) != NGX_OK) {
            return NGX_ERROR;
        }

        if (r->valid_location) {
            buf->last = ngx_copy(buf->last, alcf->uri.data, alcf->uri.len);
        }

        if (escape) {
            ngx_escape_uri(buf->last, r->uri.data + loc_len,
                           r->uri.len - loc_len, NGX_ESCAPE_URI);
            buf->last += r->uri.len - loc_len + escape;

        } else {
            buf->last = ngx_copy(buf->last, r->uri.data + loc_len,
                                 r->uri.len - loc_len);
        }

        if (r->args.len > 0) {
            *buf->last++ = '?';
            buf->last = ngx_copy(buf->last, r->args.data, r->args.len);
        }

        *buf->last ++ = '\0';
    }

    return NGX_OK;
}



static ngx_int_t
ngx_http_ajp_response_is_complete (ngx_buf_t *buf)
{
    ngx_int_t           length;

    /* not enough packet header */
    if (NGX_AJP_HEADER_SIZE > ngx_buf_size(buf)) {
        return NGX_AGAIN;
    }

    /* response packet must begin with AB 0x41 0x42 */
    if (buf->pos[0] != 'A' || buf->pos[1] != 'B') {
        return NGX_ERROR;
    }

    /* data length */
    length = ngx_http_ajp_2char_integer(&buf->pos[NGX_AJP_LENGTH_POS]);

    /* not enough packet data */
    if (length + NGX_AJP_HEADER_SIZE > ngx_buf_size(buf)) {
        return NGX_AGAIN;
    }

    return NGX_OK;
}


static inline ngx_int_t
ngx_http_ajp_response_parse_integer(ngx_buf_t *buf, ngx_uint_t *i)
{
    *i = (*buf->pos++ & 0xFF) << 8;
    *i += (*buf->pos++ & 0xFF);
    return *i;
}

static inline ngx_int_t
ngx_http_ajp_response_parse_check_head(ngx_buf_t *buf)
{
    if ( (*buf->pos ++ == 'A') && (*buf->pos ++ == 'B') ) {
        return NGX_OK;
    }

    return NGX_ERROR;
}


static inline ngx_int_t
ngx_http_ajp_response_parse_byte(ngx_buf_t *buf, u_char *c)
{
    *c  = *buf->pos++;
    return NGX_OK;
}

static inline ngx_int_t
ngx_http_ajp_response_parse_size(ngx_buf_t *buf)
{
    return buf->last - buf->pos;
}

static ngx_int_t
ngx_http_ajp_response_parse_string(ngx_buf_t *buf, ngx_str_t *str, ngx_pool_t *pool)
{
    //    ngx_int_t      rc;
    ngx_uint_t     size;
    //    rc = ngx_http_ajp_response_parse_space_enough(buf, 2);

    //    if ( rc != NGX_OK) {
    //        return rc;
    //    }

    size = (buf->pos[0] & 0xFF) << 8;
    size += (buf->pos[1] & 0xFF);

    //    rc = ngx_http_ajp_response_parse_space_enough(buf, size + 3);

    //    if ( rc != NGX_OK) {
    //        return rc;
    //    }

    str->len = size;
    str->data = ngx_palloc(pool, size);

    if (str->data == NULL) {
        return NGX_ERROR;
    }

    ngx_memcpy(str->data, buf->pos + 2, size);
    buf->pos += 3 + size;
    return NGX_OK;
}

static ngx_int_t
ngx_http_ajp_response_parse_header(ngx_buf_t *buf, ngx_table_elt_t *h, ngx_pool_t *pool)
{
    ngx_int_t           rc;

    static const char *ajp_headers[] = {
        "Content-Type",
        "Content-Language",
        "Content-Length",
        "Date",
        "Last-Modified",
        "Location",
        "Set-Cookie",
        "Set-Cookie2",
        "Servlet-Engine",
        "Status",
        "WWW-Authenticate"
    };

    if (buf->pos[0] == 0xA0) {
        /* common header */
        switch (buf->pos[1]) {
        case 0x01:
        case 0x02:
        case 0x03:
        case 0x04:
        case 0x05:
        case 0x06:
        case 0x07:
        case 0x08:
        case 0x09:
        case 0x0A:
        case 0x0B:
            ngx_str_set(&h->key, ajp_headers[buf->pos[1]]);
            buf->pos += 2;
            break;

        default:
            return NGX_ERROR;
        }
    } else {
        rc = ngx_http_ajp_response_parse_string(buf, &h->key, pool);

        if (rc != NGX_OK) {
            return NGX_ERROR;
        }

    }

    rc = ngx_http_ajp_response_parse_string(buf, &h->value, pool);

    if (rc != NGX_OK) {
        return NGX_ERROR;
    }

    h->lowcase_key = ngx_palloc(pool, h->key.len);

    if (h->lowcase_key == NULL) {
        return NGX_ERROR;
    }

    ngx_cpystrn(h->lowcase_key, h->key.data, h->key.len);
    ngx_strlow(h->lowcase_key, h->key.data, h->key.len);

    return NGX_OK;
}

static ngx_int_t
ngx_http_ajp_init_forward_request(ngx_chain_t **chain, ngx_http_request_t *r)
{
    ngx_http_ajp_loc_conf_t    *alcf;
    ngx_buf_t                  *buf;
    ngx_chain_t                *cl;
    ngx_int_t                   rc;

    // TODO:    ngx_http_script_engine_t    e, le;

    u_char                      method;
    u_char                      is_ssl;
    ngx_http_core_srv_conf_t   *cscf;
    u_short                     port;
    struct sockaddr_in         *sin;
#if (NGX_HAVE_INET6)
    struct sockaddr_in6        *sin6;
#endif

    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
    alcf = ngx_http_get_module_loc_conf(r, ngx_http_ajp_module);

    buf = ngx_create_temp_buf(r->pool, NGX_AJP_PACKET_SIZE);

    if (buf == NULL) {
        return NGX_ERROR;
    }

    cl = ngx_alloc_chain_link(r->pool);

    if (cl == NULL) {
        return NGX_ERROR;
    }

    cl->buf = buf;

    /* get method code */
    method = ngx_http_ajp_get_method(r);

    if ( method == AJP_METHOD_UNKNOWN ) {
        return NGX_DECLINED;
    }

    /* get port */
    switch (r->connection->local_sockaddr->sa_family) {

#if (NGX_HAVE_INET6)

    case AF_INET6:
        sin6 = (struct sockaddr_in6 *) r->connection->local_sockaddr;
        port = ntohs(sin6->sin6_port);
        break;
#endif

    default: /* AF_INET */
        sin = (struct sockaddr_in *) r->connection->local_sockaddr;
        port = ntohs(sin->sin_port);
        break;
    }

    /* is ssl? */
    is_ssl = 0;
#if (NGX_HTTP_SSL)

    if (r->connection->ssl) {
        is_ssl = 1;
    }

#endif


    if (ngx_http_ajp_request_init(buf) != NGX_OK
            || ngx_http_ajp_request_append_byte(buf, AJP_PACKET_FORWARD_REQUEST) != NGX_OK
            || ngx_http_ajp_request_append_byte(buf, method) != NGX_OK
            || ngx_http_ajp_request_append_string(buf, &r->http_protocol) != NGX_OK
            || ngx_http_ajp_request_append_uri(buf, r) != NGX_OK
            || ngx_http_ajp_request_append_string(buf, &r->connection->addr_text) != NGX_OK
            // TODO: remote_host
            || ngx_http_ajp_request_append_string(buf, &r->connection->addr_text) != NGX_OK
            || ngx_http_ajp_request_append_string(buf, &cscf->server_name) != NGX_OK
            || ngx_http_ajp_request_append_integer(buf, port) != NGX_OK
            || ngx_http_ajp_request_append_bool(buf, is_ssl) != NGX_OK
            || ngx_http_ajp_request_append_headers(buf, &r->headers_in.headers) != NGX_OK
            || ngx_http_ajp_request_append_attributes(buf, r) != NGX_OK
            || ngx_http_ajp_request_append_terminator(buf) != NGX_OK
            || ngx_http_ajp_request_completion(buf) != NGX_OK) {
        return NGX_ERROR;
    }

    *chain = cl;

    /* request body followed by forward request */
    rc = ngx_http_ajp_init_data(&cl->next, r);

    if (rc == NGX_OK || rc == NGX_AGAIN) {
        return NGX_OK;
    }

    return rc;
}


static ngx_int_t
ngx_http_ajp_init_data(ngx_chain_t **chain, ngx_http_request_t *r)
{
    ngx_http_ajp_ctx_t         *ctx;
    ngx_http_ajp_loc_conf_t    *alcf;
    size_t                      size, max_size;
    ngx_buf_t                  *b;
    ngx_chain_t                *cl, *ln;

    ctx = ngx_http_get_module_ctx(r, ngx_http_ajp_module);
    alcf = ngx_http_get_module_loc_conf(r, ngx_http_ajp_module);

    // TODO: check header length if !pass_request_body
    if (ctx->rest == 0 || !alcf->upstream.pass_request_body) {
        *chain = NULL;
        return NGX_DECLINED;
    }

    /* 2 bytes for size */
    max_size = alcf->max_packet_size - NGX_AJP_HEADER_SIZE - 2;

    if (ctx->rest <= max_size) {
        size = ctx->rest;
        ln = ctx->body_rest;
        ctx->rest = 0;
        ctx->body_rest = NULL;
    } else {
        size = 0;
        ln = ctx->body_rest;

        while (ngx_buf_size(ln->buf) + size <= max_size) {

            size += ngx_buf_size(ln->buf);
            ln = ln->next;
        }

        b = ngx_alloc_buf(r->pool);

        if (b == NULL) {
            return NGX_ERROR;
        }

        cl = ngx_alloc_chain_link(r->pool);

        if (cl == NULL) {
            return NGX_ERROR;
        }

        ngx_memcpy(b, ln->buf, sizeof(ngx_buf_t));

        /* split buffer */
        if (ln->buf->in_file) {
            ln->buf->file_pos += max_size - size;
            b->file_last = ln->buf->file_pos;
        } else {
            ln->buf->pos += max_size - size;
            b->last = ln->buf->pos;
        }

        cl->buf = b;
        cl->next = ln->next;
        ln->next = NULL;
        ln = ctx->body_rest;
        ctx->body_rest = cl;
        ctx->rest -= max_size;
    }

    b = ngx_create_temp_buf(ctx->request->pool, NGX_AJP_HEADER_SIZE + 2);

    if (b == NULL) {
        return NGX_ERROR;
    }

    /* header  0x12 0x34 */
    *b->last++ = (u_char) 0x12;
    *b->last++ = (u_char) 0x34;

    /* header length */
    *b->last++ = (u_char) ((size + 2) >> 8) & 0xFF;
    *b->last++ = (u_char) (size + 2) & 0xFF;

    /* data length */
    *b->last++ = (u_char) (size >> 8) & 0xFF;
    *b->last++ = (u_char) size & 0xFF;

    cl = ngx_alloc_chain_link(r->pool);

    if (cl == NULL) {
        return NGX_ERROR;
    }

    cl->buf = b;
    cl->next = ln;
    *chain = cl;

    if (ctx->rest) {
        return NGX_AGAIN;
    }

    return NGX_OK;
}

// TODO:
/*
static ngx_int_t
ngx_http_ajp_chain_ping(ngx_chain_t **chain)
{
    if (ngx_http_ajp_request_init(buf) != NGX_OK
            || ngx_http_ajp_request_append_byte(buf, AJP_PACKET_PING) != NGX_OK
            || ngx_http_ajp_request_completion(buf) != NGX_OK) {
        return NGX_ERROR;
    }

    return NGX_OK;
}
*/
// TODO:
#if 0
static ngx_int_t
ngx_http_ajp_init_cping(ngx_buf_t *buf)
{
    if (ngx_http_ajp_request_init(buf) != NGX_OK
            || ngx_http_ajp_request_append_byte(buf, AJP_PACKET_CPING) != NGX_OK
            || ngx_http_ajp_request_completion(buf) != NGX_OK) {
        return NGX_ERROR;
    }

    return NGX_OK;
}
#endif
static ngx_int_t
ngx_http_ajp_process_send_headers(ngx_buf_t *buf, ngx_http_ajp_ctx_t *ctx)
{
    ngx_int_t                       rc, length;
    ngx_uint_t                      header_num, i, len;
    ngx_table_elt_t                *h;
    ngx_http_upstream_t            *u;
    ngx_http_request_t             *r;
    ngx_http_upstream_header_t     *hh;
    ngx_http_upstream_main_conf_t  *umcf;
    u_char                         *p;
    u_char                         *pos, *last;

    r = ctx->request;

    umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);

    u = r->upstream;

    /* HEADER CHECK already processed */
    /* length */
    length = ngx_http_ajp_2char_integer(&buf->pos[NGX_AJP_LENGTH_POS]);

    /* save left over data pointer */
    pos = buf->pos + NGX_AJP_HEADER_SIZE + length;
    last = buf->last;

    /* type allready processed */
    buf->pos += NGX_AJP_TYPE_POS + 1;
    buf->last = pos;

    /* status code */
    rc = ngx_http_ajp_response_parse_integer(buf, &u->headers_in.status_n);

    u->state->status = u->headers_in.status_n;

    /* status line */
    rc = ngx_http_ajp_response_parse_integer(buf, &len);
    p = ngx_pcalloc(r->pool, len + 4);

    if (p == NULL) {
        return NGX_ERROR;
    }

    (void) ngx_snprintf(p, len + 4, "%d %s", u->headers_in.status_n, buf->pos);

    u->headers_in.status_line.data = p;
    u->headers_in.status_line.len = len + 4;

    buf->pos += len + 1;

    /* headers */
    rc = ngx_http_ajp_response_parse_integer(buf, &header_num);

    for (i = 0; i < header_num; i++) {
        h = ngx_list_push(&u->headers_in.headers);

        if (h == NULL) {
            return NGX_ERROR;
        }

        h->hash = r->header_hash;

        rc = ngx_http_ajp_response_parse_header(buf, h, r->pool);

        if (rc != NGX_OK) {
            return NGX_ERROR;
        }

        hh = ngx_hash_find(&umcf->headers_in_hash, h->hash,
                           h->lowcase_key, h->key.len);

        if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
            return NGX_ERROR;
        }

        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "http ajp header: \"%V: %V\"",
                       &h->key, &h->value);
    }

    /* a whole header has been parsed successfully */

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http proxy header done");

    /*
     * if no "Server" and "Date" in header line,
     * then add the special empty headers
     */

    if (r->upstream->headers_in.server == NULL) {
        h = ngx_list_push(&r->upstream->headers_in.headers);

        if (h == NULL) {
            return NGX_ERROR;
        }

        h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash(
                ngx_hash('s', 'e'), 'r'), 'v'), 'e'), 'r');

        ngx_str_set(&h->key, "Server");
        ngx_str_null(&h->value);
        h->lowcase_key = (u_char *) "server";
    }

    if (r->upstream->headers_in.date == NULL) {
        h = ngx_list_push(&r->upstream->headers_in.headers);

        if (h == NULL) {
            return NGX_ERROR;
        }

        h->hash = ngx_hash(ngx_hash(ngx_hash('d', 'a'), 't'), 'e');

        ngx_str_set(&h->key, "Date");
        ngx_str_null(&h->value);
        h->lowcase_key = (u_char *) "date";
    }

    /* clear content length if response is chunked */

    u = r->upstream;

    if (u->headers_in.chunked) {
        u->headers_in.content_length_n = -1;
    }

    /*
     * set u->keepalive if response has no body; this allows to keep
     * connections alive in case of r->header_only or X-Accel-Redirect
     */
    if (u->headers_in.status_n == NGX_HTTP_NO_CONTENT
            || u->headers_in.status_n == NGX_HTTP_NOT_MODIFIED
            || (!u->headers_in.chunked
                && u->headers_in.content_length_n == 0)) {
        u->keepalive = !u->headers_in.connection_close;
    }

    /* process next packet */
    buf->pos = pos;
    buf->last = last;
    ngx_buf_trim(buf);
#if 0
    rc = ngx_http_ajp_process_response(ctx);

    if ( rc == NGX_AGAIN) {
        ngx_buf_trim(buf);
    }
    if (rc == NGX_ERROR) {
        return NGX_ERROR;
    }
#endif
    return NGX_OK;
}


static ngx_int_t
ngx_http_ajp_process_get_body_chunk (ngx_buf_t *buf, ngx_http_ajp_ctx_t *ctx)
{
    ngx_int_t           rc, length;
    ngx_buf_t          *b;
    ngx_http_request_t *r;
    u_char             *pos, *last;

    /* HEADER CHECK already processed */
    /* length */
    length = ngx_http_ajp_2char_integer(&buf->pos[NGX_AJP_LENGTH_POS]);

    /* save left over data pointer */
    pos = buf->pos + NGX_AJP_HEADER_SIZE + length;
    last = buf->last;

    /* type allready processed */
    buf->pos += NGX_AJP_TYPE_POS + 1;
    buf->last = pos;

    /* request chunk length */

    r = ctx->request;

    if (ctx->rest == 0) {
        /* send empty packet */
        b = ngx_create_temp_buf(r->pool, 4);

        if (b == NULL) {
            return NGX_ERROR;
        }

        *b->last ++ = 0x12;
        *b->last ++ = 0x34;
        *b->last ++ = 0x00;
        *b->last ++ = 0x00;
        ctx->packet = ngx_alloc_chain_link(r->pool);

        if (ctx->packet == NULL) {
            return NGX_ERROR;
        }

        ctx->packet->buf = b;
    } else {
        /* send packet */
        ctx->packet = NULL;
        rc = ngx_http_ajp_init_data(&ctx->packet, ctx->request);

        if (rc != NGX_OK) {
            return NGX_ERROR;
        }
    }

    ngx_http_ajp_send_request_packet(ctx, ctx->packet);

    /* process next packet */
    buf->pos = pos;
    buf->last = last;

    rc = ngx_http_ajp_process_response(ctx);

    if ( rc == NGX_AGAIN) {
        ngx_buf_trim(buf);
    }

    return rc;
}


static ngx_int_t
ngx_http_ajp_process_send_body_chunk (ngx_buf_t *buf, ngx_http_ajp_ctx_t *ctx)
{
    ngx_uint_t           chunk_len, length, rest;
    ngx_chain_t         *cl, **ll;
    ngx_http_upstream_t *u;
    u_char              *start, *end;

    /* packet data length */
    length = ngx_http_ajp_2char_integer(&buf->pos[NGX_AJP_LENGTH_POS]);
    /* chunk len */
    chunk_len = ngx_http_ajp_2char_integer(&buf->pos[NGX_AJP_RESPONSE_DATA_POS]);

    if (chunk_len + NGX_AJP_RESPONSE_DATA_POS > length + NGX_AJP_LENGTH_POS ) {
        return NGX_ERROR;
    }

    u = ctx->request->upstream;

    /* append packet data to out_bufs */
    for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) {
        ll = &cl->next;
    }

    /* get chain */
    cl = ngx_chain_get_free_buf(ctx->request->pool, &u->free_bufs);

    if (cl == NULL) {
        return NGX_ERROR;
    }

    /* new buffer */
    if (cl->buf->start && ngx_buf_size(cl->buf) >= NGX_AJP_PACKET_SIZE) {
        start = cl->buf->start;
        end = cl->buf->end;
    } else {
        start = ngx_palloc(ctx->request->pool, NGX_AJP_PACKET_SIZE);
        end = start + NGX_AJP_PACKET_SIZE;
    }

    /* move all the leftover data to the beginning of the new buffer */
    rest = buf->last - buf->pos - length - NGX_AJP_HEADER_SIZE;
    ngx_memcpy(start, buf->pos + length + NGX_AJP_HEADER_SIZE, rest);

    /* append  */
    *ll = cl;
    *cl->buf = *buf;
    cl->buf->pos += NGX_AJP_CHUNK_DATA_POS;
    cl->buf->last = cl->buf->pos + chunk_len;
    cl->buf->flush = 1;

    /*  */
    buf->start = start;
    buf->pos = start;
    buf->last = buf->pos + rest;
    buf->end = end;

    u->length -= chunk_len;

    return ngx_http_ajp_process_response(ctx);
}


static ngx_int_t
ngx_http_ajp_process_end_response (ngx_buf_t *buf, ngx_http_ajp_ctx_t *ctx)
{
    ngx_http_upstream_t       *u = ctx->request->upstream;

    if (u->length != NGX_HTTP_AJP_END) {
        // TODO: log error.
    }

    u->length = 0;

    // TODO: set keepalive
    /* reuse */
    if (buf->pos[NGX_AJP_RESPONSE_DATA_POS]) {
        u->keepalive = 1;
    }
    
    buf->pos += NGX_HTTP_AJP_END;

    return NGX_OK;
}

static ngx_int_t
ngx_http_ajp_process_cpong(ngx_buf_t *buf, ngx_http_ajp_ctx_t *ctx)
{
    // TODO:
    if (ctx->request_type != AJP_PACKET_CPING) {
        return NGX_ERROR;
    }

    // TODO:
    return NGX_OK;
}

static ngx_int_t
ngx_http_ajp_process_response(ngx_http_ajp_ctx_t *ctx)
{
    ngx_http_request_t  *r;
    ngx_buf_t           *buf;
    u_char               type;
    ngx_int_t            rc;
    ngx_http_upstream_t *u;

    r = ctx->request;
    u = r->upstream;
    buf = &u->buffer;

    rc = ngx_http_ajp_response_is_complete(buf);

    if (rc == NGX_ERROR) {
        return rc;
    }

    if (rc == NGX_AGAIN) {
        ngx_buf_trim(buf);
        return rc;
    }

    /* rc == NGX_OK */

    /* complete response received */
    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "ajp: response received");

    /* response packet type */
    type = buf->pos[NGX_AJP_TYPE_POS];

    switch (type) {
    case AJP_PACKET_SEND_BODY_CHUNK:

        return ngx_http_ajp_process_send_body_chunk(buf, ctx);
        break;

    case AJP_PACKET_SEND_HEADERS:

        return ngx_http_ajp_process_send_headers(buf, ctx);
        break;

    case AJP_PACKET_GET_BODY_CHUNK:

        return ngx_http_ajp_process_get_body_chunk(buf, ctx);
        break;

    case AJP_PACKET_CPONG_REPLY:
        return ngx_http_ajp_process_cpong(buf, ctx);
        break;

    case AJP_PACKET_END_RESPONSE:
        /* return we don't need to process next packet */
        return ngx_http_ajp_process_end_response(buf, ctx);
        break;

    default:
        return NGX_ERROR;
    }

    return NGX_OK;
}


static ngx_int_t
ngx_http_ajp_handler(ngx_http_request_t *r)
{
    ngx_int_t                   rc;
    ngx_http_upstream_t        *u;
    ngx_http_ajp_ctx_t         *ctx;
    ngx_http_ajp_loc_conf_t    *alcf;

    if (ngx_http_upstream_create(r) != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    // TODO: init ctx
    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_ajp_ctx_t));

    if (ctx == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    /*
       * set by ngx_pcalloc():
       *
       *
       */
    ctx->request = r;
    ctx->rest = r->headers_in.content_length_n;

    ngx_http_set_ctx(r, ctx, ngx_http_ajp_module);


    alcf = ngx_http_get_module_loc_conf(r, ngx_http_ajp_module);

    if (alcf->ajp_lengths) {
        if (ngx_http_ajp_eval(r, alcf) != NGX_OK) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }
    }

    u = r->upstream;

    ngx_str_set(&u->schema, "ajp://");
    u->output.tag = (ngx_buf_tag_t) &ngx_http_ajp_module;
    u->conf = &alcf->upstream;

#if (NGX_HTTP_CACHE)
    // TODO: u->create_key = ngx_http_ajp_create_key;
#endif
    u->create_request = ngx_http_ajp_create_request;
    u->reinit_request = ngx_http_ajp_reinit_request;
    u->process_header = ngx_http_ajp_process_header;
    u->abort_request = ngx_http_ajp_abort_request;
    u->finalize_request = ngx_http_ajp_finalize_request;

    u->input_filter_init = ngx_http_ajp_filter_init;
    u->input_filter = ngx_http_ajp_filter;
    u->input_filter_ctx = ctx;

    rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);

    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
        return rc;
    }

    return NGX_DONE;
}

static ngx_int_t
ngx_http_ajp_filter(void *data, ssize_t bytes)
{
    ngx_http_ajp_ctx_t   *ctx = data;

    ctx->request->upstream->buffer.last += bytes;

    return ngx_http_ajp_process_response(ctx);
}

static ngx_int_t
ngx_http_ajp_filter_init(void *data)
{
    ngx_http_ajp_ctx_t   *ctx = data;

    ngx_http_upstream_t  *u;

    u = ctx->request->upstream;

    // TODO:

    u->length += NGX_HTTP_AJP_END;
    return NGX_OK;
}


static void *
ngx_http_ajp_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_ajp_loc_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ajp_loc_conf_t));

    if (conf == NULL) {
        return NULL;
    }

    conf->upstream.store = NGX_CONF_UNSET;
    conf->upstream.store_access = NGX_CONF_UNSET_UINT;
    conf->upstream.buffering = NGX_CONF_UNSET;
    conf->upstream.ignore_client_abort = NGX_CONF_UNSET;

    conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC;
    conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC;
    conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC;

    conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE;
    conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE;

    conf->upstream.busy_buffers_size_conf = NGX_CONF_UNSET_SIZE;
    conf->upstream.max_temp_file_size_conf = NGX_CONF_UNSET_SIZE;
    conf->upstream.temp_file_write_size_conf = NGX_CONF_UNSET_SIZE;

    conf->upstream.pass_request_headers = NGX_CONF_UNSET;
    conf->upstream.pass_request_body = NGX_CONF_UNSET;
#if 0
#if (NGX_HTTP_CACHE)
    conf->upstream.cache = NGX_CONF_UNSET_PTR;
    conf->upstream.cache_min_uses = NGX_CONF_UNSET_UINT;
    conf->upstream.cache_bypass = NGX_CONF_UNSET_PTR;
    conf->upstream.no_cache = NGX_CONF_UNSET_PTR;
    conf->upstream.cache_valid = NGX_CONF_UNSET_PTR;
    conf->upstream.cache_lock = NGX_CONF_UNSET;
    conf->upstream.cache_lock_timeout = NGX_CONF_UNSET_MSEC;
#endif
#endif
    conf->upstream.hide_headers = NGX_CONF_UNSET_PTR;
    conf->upstream.pass_headers = NGX_CONF_UNSET_PTR;

    conf->upstream.intercept_errors = NGX_CONF_UNSET;

    /* "ajp_cyclic_temp_file" is disabled */
    conf->upstream.cyclic_temp_file = 0;

    conf->upstream.change_buffering = 1;

    ngx_str_set(&conf->upstream.module, "ajp");

    return conf;
}


static char *
ngx_http_ajp_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_ajp_loc_conf_t *prev = parent;
    ngx_http_ajp_loc_conf_t *conf = child;

    size_t                        size;
    ngx_hash_init_t               hash;
    ngx_http_core_loc_conf_t     *clcf;

    if (conf->upstream.store != 0) {
        ngx_conf_merge_value(conf->upstream.store, prev->upstream.store, 0);

        if (conf->upstream.store_lengths == NULL) {
            conf->upstream.store_lengths = prev->upstream.store_lengths;
            conf->upstream.store_values = prev->upstream.store_values;
        }
    }

    ngx_conf_merge_uint_value(conf->upstream.store_access,
                              prev->upstream.store_access, 0600);

    ngx_conf_merge_value(conf->upstream.buffering,
                         prev->upstream.buffering, 1);

    ngx_conf_merge_value(conf->upstream.ignore_client_abort,
                         prev->upstream.ignore_client_abort, 0);

    ngx_conf_merge_msec_value(conf->upstream.connect_timeout,
                              prev->upstream.connect_timeout, 60000);

    ngx_conf_merge_msec_value(conf->upstream.send_timeout,
                              prev->upstream.send_timeout, 60000);

    ngx_conf_merge_msec_value(conf->upstream.read_timeout,
                              prev->upstream.read_timeout, 60000);

    ngx_conf_merge_size_value(conf->upstream.send_lowat,
                              prev->upstream.send_lowat, 0);

    ngx_conf_merge_size_value(conf->upstream.buffer_size,
                              prev->upstream.buffer_size,
                              (size_t) NGX_AJP_PACKET_SIZE);

    ngx_conf_merge_bufs_value(conf->upstream.bufs, prev->upstream.bufs,
                              8, NGX_AJP_PACKET_SIZE);

    if (conf->upstream.bufs.num < 2) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "there must be at least 2 \"ajp_buffers\"");
        return NGX_CONF_ERROR;
    }


    size = conf->upstream.buffer_size;

    if (size < conf->upstream.bufs.size) {
        size = conf->upstream.bufs.size;
    }


    ngx_conf_merge_size_value(conf->upstream.busy_buffers_size_conf,
                              prev->upstream.busy_buffers_size_conf,
                              NGX_CONF_UNSET_SIZE);

    if (conf->upstream.busy_buffers_size_conf == NGX_CONF_UNSET_SIZE) {
        conf->upstream.busy_buffers_size = 2 * size;
    } else {
        conf->upstream.busy_buffers_size =
            conf->upstream.busy_buffers_size_conf;
    }

    if (conf->upstream.busy_buffers_size < size) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "\"ajp_busy_buffers_size\" must be equal or bigger "
                           "than maximum of the value of \"ajp_buffer_size\" and "
                           "one of the \"ajp_buffers\"");

        return NGX_CONF_ERROR;
    }

    if (conf->upstream.busy_buffers_size
            > (conf->upstream.bufs.num - 1) * conf->upstream.bufs.size) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "\"ajp_busy_buffers_size\" must be less than "
                           "the size of all \"ajp_buffers\" minus one buffer");

        return NGX_CONF_ERROR;
    }


    ngx_conf_merge_size_value(conf->upstream.temp_file_write_size_conf,
                              prev->upstream.temp_file_write_size_conf,
                              NGX_CONF_UNSET_SIZE);

    if (conf->upstream.temp_file_write_size_conf == NGX_CONF_UNSET_SIZE) {
        conf->upstream.temp_file_write_size = 2 * size;
    } else {
        conf->upstream.temp_file_write_size =
            conf->upstream.temp_file_write_size_conf;
    }

    if (conf->upstream.temp_file_write_size < size) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "\"ajp_temp_file_write_size\" must be equal or bigger than "
                           "maximum of the value of \"ajp_buffer_size\" and "
                           "one of the \"ajp_buffers\"");

        return NGX_CONF_ERROR;
    }


    ngx_conf_merge_size_value(conf->upstream.max_temp_file_size_conf,
                              prev->upstream.max_temp_file_size_conf,
                              NGX_CONF_UNSET_SIZE);

    if (conf->upstream.max_temp_file_size_conf == NGX_CONF_UNSET_SIZE) {
        conf->upstream.max_temp_file_size = 1024 * 1024 * 1024;
    } else {
        conf->upstream.max_temp_file_size =
            conf->upstream.max_temp_file_size_conf;
    }

    if (conf->upstream.max_temp_file_size != 0
            && conf->upstream.max_temp_file_size < size) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "\"ajp_max_temp_file_size\" must be equal to zero to disable "
                           "the temporary files usage or must be equal or bigger than "
                           "maximum of the value of \"ajp_buffer_size\" and "
                           "one of the \"ajp_buffers\"");

        return NGX_CONF_ERROR;
    }


    ngx_conf_merge_bitmask_value(conf->upstream.ignore_headers,
                                 prev->upstream.ignore_headers,
                                 NGX_CONF_BITMASK_SET);


    ngx_conf_merge_bitmask_value(conf->upstream.next_upstream,
                                 prev->upstream.next_upstream,
                                 (NGX_CONF_BITMASK_SET
                                  |NGX_HTTP_UPSTREAM_FT_ERROR
                                  |NGX_HTTP_UPSTREAM_FT_TIMEOUT));

    if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) {
        conf->upstream.next_upstream = NGX_CONF_BITMASK_SET
                                       |NGX_HTTP_UPSTREAM_FT_OFF;
    }

    /*
        if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path,
                                      prev->upstream.temp_path,
                                      &ngx_http_ajp_temp_path)
                != NGX_OK) {
            return NGX_CONF_ERROR;
        }
    */
#if (NGX_HTTP_CACHE)

    ngx_conf_merge_ptr_value(conf->upstream.cache,
                             prev->upstream.cache, NULL);

    if (conf->upstream.cache && conf->upstream.cache->data == NULL) {
        ngx_shm_zone_t  *shm_zone;

        shm_zone = conf->upstream.cache;

        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "\"ajp_cache\" zone \"%V\" is unknown",
                           &shm_zone->shm.name);

        return NGX_CONF_ERROR;
    }

    ngx_conf_merge_uint_value(conf->upstream.cache_min_uses,
                              prev->upstream.cache_min_uses, 1);

    ngx_conf_merge_bitmask_value(conf->upstream.cache_use_stale,
                                 prev->upstream.cache_use_stale,
                                 (NGX_CONF_BITMASK_SET
                                  |NGX_HTTP_UPSTREAM_FT_OFF));

    if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_OFF) {
        conf->upstream.cache_use_stale = NGX_CONF_BITMASK_SET
                                         |NGX_HTTP_UPSTREAM_FT_OFF;
    }

    if (conf->upstream.cache_use_stale & NGX_HTTP_UPSTREAM_FT_ERROR) {
        conf->upstream.cache_use_stale |= NGX_HTTP_UPSTREAM_FT_NOLIVE;
    }

    if (conf->upstream.cache_methods == 0) {
        conf->upstream.cache_methods = prev->upstream.cache_methods;
    }

    conf->upstream.cache_methods |= NGX_HTTP_GET|NGX_HTTP_HEAD;

    ngx_conf_merge_ptr_value(conf->upstream.cache_bypass,
                             prev->upstream.cache_bypass, NULL);

    ngx_conf_merge_ptr_value(conf->upstream.no_cache,
                             prev->upstream.no_cache, NULL);

    ngx_conf_merge_ptr_value(conf->upstream.cache_valid,
                             prev->upstream.cache_valid, NULL);

    if (conf->cache_key.value.data == NULL) {
        conf->cache_key = prev->cache_key;
    }

    ngx_conf_merge_value(conf->upstream.cache_lock,
                         prev->upstream.cache_lock, 0);

    ngx_conf_merge_msec_value(conf->upstream.cache_lock_timeout,
                              prev->upstream.cache_lock_timeout, 5000);

#endif

    ngx_conf_merge_value(conf->upstream.pass_request_headers,
                         prev->upstream.pass_request_headers, 1);
    ngx_conf_merge_value(conf->upstream.pass_request_body,
                         prev->upstream.pass_request_body, 1);

    ngx_conf_merge_value(conf->upstream.intercept_errors,
                         prev->upstream.intercept_errors, 0);

    hash.max_size = 512;
    hash.bucket_size = ngx_align(64, ngx_cacheline_size);
    hash.name = "ajp_hide_headers_hash";

    if (ngx_http_upstream_hide_headers_hash(cf, &conf->upstream,
                                            &prev->upstream, ngx_http_ajp_hide_headers, &hash)
            != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    if (conf->upstream.upstream == NULL) {
        conf->upstream.upstream = prev->upstream.upstream;
    }

    if (conf->ajp_lengths == NULL) {
        conf->ajp_lengths = prev->ajp_lengths;
        conf->ajp_values = prev->ajp_values;
    }

    if (conf->upstream.upstream || conf->ajp_lengths) {
        clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);

        if (clcf->handler == NULL && clcf->lmt_excpt) {
            clcf->handler = ngx_http_ajp_handler;
        }
    }

    if (ngx_http_ajp_merge_params(cf, conf, prev) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    return NGX_CONF_OK;
}


static ngx_int_t
ngx_http_ajp_merge_params(ngx_conf_t *cf, ngx_http_ajp_loc_conf_t *conf,
                          ngx_http_ajp_loc_conf_t *prev)
{
    u_char                       *p;
    size_t                        size;
    uintptr_t                    *code;
    ngx_uint_t                    i, nsrc;
    ngx_array_t                   headers_names;
#if 0
#if (NGX_HTTP_CACHE)
    ngx_array_t                   params_merged;
#endif
#endif
    ngx_hash_key_t               *hk;
    ngx_hash_init_t               hash;
    ngx_http_upstream_param_t    *src;
    ngx_http_script_compile_t     sc;
    ngx_http_script_copy_code_t  *copy;

    if (conf->params_source == NULL) {
        conf->params_source = prev->params_source;

        if (prev->headers_hash.buckets
#if (NGX_HTTP_CACHE)
                && ((conf->upstream.cache == NULL) == (prev->upstream.cache == NULL))
#endif
           ) {
            conf->flushes = prev->flushes;
            conf->params_len = prev->params_len;
            conf->params = prev->params;
            conf->headers_hash = prev->headers_hash;
            conf->header_params = prev->header_params;

            return NGX_OK;
        }
    }

    if (conf->params_source == NULL
#if (NGX_HTTP_CACHE)
            && (conf->upstream.cache == NULL)
#endif
       ) {
        conf->headers_hash.buckets = (void *) 1;
        return NGX_OK;
    }

    conf->params_len = ngx_array_create(cf->pool, 64, 1);

    if (conf->params_len == NULL) {
        return NGX_ERROR;
    }

    conf->params = ngx_array_create(cf->pool, 512, 1);

    if (conf->params == NULL) {
        return NGX_ERROR;
    }

    if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t))
            != NGX_OK) {
        return NGX_ERROR;
    }

    if (conf->params_source) {
        src = conf->params_source->elts;
        nsrc = conf->params_source->nelts;

    } else {
        src = NULL;
        nsrc = 0;
    }

#if 0
#if (NGX_HTTP_CACHE)

    if (conf->upstream.cache) {
        ngx_keyval_t               *h;
        ngx_http_upstream_param_t  *s;

        if (ngx_array_init(&params_merged, cf->temp_pool, 4,
                           sizeof(ngx_http_upstream_param_t))
                != NGX_OK) {
            return NGX_ERROR;
        }

        for (i = 0; i < nsrc; i++) {

            s = ngx_array_push(&params_merged);

            if (s == NULL) {
                return NGX_ERROR;
            }

            *s = src[i];
        }

        h = ngx_http_ajp_cache_headers;

        while (h->key.len) {

            src = params_merged.elts;
            nsrc = params_merged.nelts;

            for (i = 0; i < nsrc; i++) {
                if (ngx_strcasecmp(h->key.data, src[i].key.data) == 0) {
                    goto next;
                }
            }

            s = ngx_array_push(&params_merged);

            if (s == NULL) {
                return NGX_ERROR;
            }

            s->key = h->key;
            s->value = h->value;
            s->skip_empty = 0;

next:

            h++;
        }

        src = params_merged.elts;
        nsrc = params_merged.nelts;
    }

#endif
#endif

    for (i = 0; i < nsrc; i++) {

        if (src[i].key.len > sizeof("HTTP_") - 1
                && ngx_strncmp(src[i].key.data, "HTTP_", sizeof("HTTP_") - 1) == 0) {
            hk = ngx_array_push(&headers_names);

            if (hk == NULL) {
                return NGX_ERROR;
            }

            hk->key.len = src[i].key.len - 5;
            hk->key.data = src[i].key.data + 5;
            hk->key_hash = ngx_hash_key_lc(hk->key.data, hk->key.len);
            hk->value = (void *) 1;

            if (src[i].value.len == 0) {
                continue;
            }
        }

        copy = ngx_array_push_n(conf->params_len,
                                sizeof(ngx_http_script_copy_code_t));

        if (copy == NULL) {
            return NGX_ERROR;
        }

        copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code;
        copy->len = src[i].key.len + 1;

        copy = ngx_array_push_n(conf->params_len,
                                sizeof(ngx_http_script_copy_code_t));

        if (copy == NULL) {
            return NGX_ERROR;
        }

        copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code;
        copy->len = src[i].skip_empty;


        size = (sizeof(ngx_http_script_copy_code_t)
                + src[i].key.len + 1 + sizeof(uintptr_t) - 1)
               & ~(sizeof(uintptr_t) - 1);

        copy = ngx_array_push_n(conf->params, size);

        if (copy == NULL) {
            return NGX_ERROR;
        }

        copy->code = ngx_http_script_copy_code;
        copy->len = src[i].key.len + 1;

        p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t);
        (void) ngx_cpystrn(p, src[i].key.data, src[i].key.len + 1);


        ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));

        sc.cf = cf;
        sc.source = &src[i].value;
        sc.flushes = &conf->flushes;
        sc.lengths = &conf->params_len;
        sc.values = &conf->params;

        if (ngx_http_script_compile(&sc) != NGX_OK) {
            return NGX_ERROR;
        }

        code = ngx_array_push_n(conf->params_len, sizeof(uintptr_t));

        if (code == NULL) {
            return NGX_ERROR;
        }

        *code = (uintptr_t) NULL;


        code = ngx_array_push_n(conf->params, sizeof(uintptr_t));

        if (code == NULL) {
            return NGX_ERROR;
        }

        *code = (uintptr_t) NULL;
    }

    code = ngx_array_push_n(conf->params_len, sizeof(uintptr_t));

    if (code == NULL) {
        return NGX_ERROR;
    }

    *code = (uintptr_t) NULL;

    code = ngx_array_push_n(conf->params, sizeof(uintptr_t));

    if (code == NULL) {
        return NGX_ERROR;
    }

    *code = (uintptr_t) NULL;

    conf->header_params = headers_names.nelts;

    hash.hash = &conf->headers_hash;
    hash.key = ngx_hash_key_lc;
    hash.max_size = 512;
    hash.bucket_size = 64;
    hash.name = "ajp_params_hash";
    hash.pool = cf->pool;
    hash.temp_pool = NULL;

    return ngx_hash_init(&hash, headers_names.elts, headers_names.nelts);
}


static char *
ngx_http_ajp_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_ajp_loc_conf_t   *alcf = conf;

    ngx_url_t                  u;
    ngx_str_t                 *value, *url;
    ngx_uint_t                 n;
    ngx_http_core_loc_conf_t  *clcf;
    ngx_http_script_compile_t  sc;

    if (alcf->upstream.upstream || alcf->ajp_lengths) {
        return "is duplicate";
    }

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);

    clcf->handler = ngx_http_ajp_handler;

    if (clcf->name.data[clcf->name.len - 1] == '/') {
        clcf->auto_redirect = 1;
    }

    value = cf->args->elts;

    url = &value[1];

    n = ngx_http_script_variables_count(url);

    if (n) {

        ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));

        sc.cf = cf;
        sc.source = url;
        sc.lengths = &alcf->ajp_lengths;
        sc.values = &alcf->ajp_values;
        sc.variables = n;
        sc.complete_lengths = 1;
        sc.complete_values = 1;

        if (ngx_http_script_compile(&sc) != NGX_OK) {
            return NGX_CONF_ERROR;
        }

        return NGX_CONF_OK;
    }

    ngx_memzero(&u, sizeof(ngx_url_t));

    u.url = value[1];
    u.no_resolve = 1;

    alcf->uri = u.uri;

    alcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);

    if (alcf->upstream.upstream == NULL) {
        return NGX_CONF_ERROR;
    }

    alcf->location = clcf->name;

    return NGX_CONF_OK;
}


static char *
ngx_http_ajp_store(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_ajp_loc_conf_t *scf = conf;

    ngx_str_t                  *value;
    ngx_http_script_compile_t   sc;

    if (scf->upstream.store != NGX_CONF_UNSET || scf->upstream.store_lengths) {
        return "is duplicate";
    }

    value = cf->args->elts;

    if (ngx_strcmp(value[1].data, "off") == 0) {
        scf->upstream.store = 0;
        return NGX_CONF_OK;
    }

#if (NGX_HTTP_CACHE)

    if (scf->upstream.cache != NGX_CONF_UNSET_PTR
            && scf->upstream.cache != NULL) {
        return "is incompatible with \"ajp_cache\"";
    }

#endif

    if (ngx_strcmp(value[1].data, "on") == 0) {
        scf->upstream.store = 1;
        return NGX_CONF_OK;
    }

    /* include the terminating '\0' into script */
    value[1].len++;

    ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));

    sc.cf = cf;
    sc.source = &value[1];
    sc.lengths = &scf->upstream.store_lengths;
    sc.values = &scf->upstream.store_values;
    sc.variables = ngx_http_script_variables_count(&value[1]);;
    sc.complete_lengths = 1;
    sc.complete_values = 1;

    if (ngx_http_script_compile(&sc) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    return NGX_CONF_OK;
}


static ngx_int_t
ngx_http_ajp_create_request(ngx_http_request_t *r)
{
    ngx_http_ajp_ctx_t           *ctx;
    ngx_int_t                     rc;


    ctx = ngx_http_get_module_ctx(r, ngx_http_ajp_module);

    /* save request body */
    ctx->body_rest = r->upstream->request_bufs;
    r->upstream->request_bufs = NULL;

    ctx->request_type = AJP_PACKET_FORWARD_REQUEST;
    rc = ngx_http_ajp_init_forward_request(&r->upstream->request_bufs, r);

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http ajp request created");

    return rc;
}


static ngx_int_t
ngx_http_ajp_reinit_request(ngx_http_request_t *r)
{
    return NGX_OK;
}


static ngx_int_t
ngx_http_ajp_process_header(ngx_http_request_t *r)
{
    ngx_http_upstream_t       *u;
    ngx_http_ajp_ctx_t        *ctx;

    u = r->upstream;
    ctx = ngx_http_get_module_ctx(r, ngx_http_ajp_module);

    return ngx_http_ajp_process_response(ctx);
}

static void
ngx_http_ajp_abort_request(ngx_http_request_t *r)
{
    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "abort http ajp request");
    return;
}


static void
ngx_http_ajp_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
{
    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "finalize http ajp request");
    return;
}

// TODO: check this function
static void
ngx_http_ajp_block_read_handler(ngx_http_request_t *r, ngx_http_upstream_t *u)
{

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http ajp block read handler");

    if (ngx_handle_read_event(u->peer.connection->read, 0) != NGX_OK) {
        ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
    }

    // ngx_add_timer(u->peer.connection->read, u->conf->read_timeout);
}


static ngx_int_t
ngx_http_ajp_eval(ngx_http_request_t *r, ngx_http_ajp_loc_conf_t *alcf)
{
    ngx_url_t             url;
    ngx_http_upstream_t  *u;

    ngx_memzero(&url, sizeof(ngx_url_t));

    if (ngx_http_script_run(r, &url.url, alcf->ajp_lengths->elts, 0,
                            alcf->ajp_values->elts)
            == NULL) {
        return NGX_ERROR;
    }

    url.no_resolve = 1;

    if (ngx_parse_url(r->pool, &url) != NGX_OK) {
        if (url.err) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "%s in upstream \"%V\"", url.err, &url.url);
        }

        return NGX_ERROR;
    }

    u = r->upstream;

    u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t));

    if (u->resolved == NULL) {
        return NGX_ERROR;
    }

    if (url.addrs && url.addrs[0].sockaddr) {
        u->resolved->sockaddr = url.addrs[0].sockaddr;
        u->resolved->socklen = url.addrs[0].socklen;
        u->resolved->naddrs = 1;
        u->resolved->host = url.addrs[0].name;

    } else {
        u->resolved->host = url.host;
        u->resolved->port = url.port;
        u->resolved->no_port = url.no_port;
    }

    return NGX_OK;
}


static void
ngx_http_ajp_send_request_packet(ngx_http_ajp_ctx_t *ctx, ngx_chain_t *packet)
{
    ngx_http_request_t        *r;
    ngx_http_upstream_t       *u;
    ngx_connection_t          *c;

    r = ctx->request;
    u = r->upstream;
    c = u->peer.connection;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http ajp send packet");

    ctx->packet = packet;

    ctx->read_ready = c->read->ready;

    /* use to break from ngx_http_upstream_process_body_in_memory */
    c->read->ready = 0;

    /*
    * save handlers
    *   ngx_http_upstream_process_non_buffered_upstream
    *   ngx_http_upstream_dummy_handler
    */
    ctx->read_event_handler = u->read_event_handler;
    ctx->write_event_handler = u->write_event_handler;

    /*
     * set new handler & block read
     */
    u->write_event_handler = ngx_http_ajp_send_request_packet_handler;
    u->read_event_handler = ngx_http_ajp_block_read_handler;

    if (c->read->timer_set) {
        ngx_del_timer(c->read);
    }

    ngx_http_ajp_send_request_packet_handler(r, u);
    return;
}

static void
ngx_http_ajp_send_request_packet_handler(ngx_http_request_t *r, ngx_http_upstream_t *u)
{
    ngx_int_t           rc;
    ngx_connection_t   *c;
    ngx_http_ajp_ctx_t *ctx;

    c = u->peer.connection;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http ajp send packet handler");

    if (c->write->timedout) {
        ngx_http_upstream_finalize_request(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT);
        return;
    }

    c->log->action = "sending packet to ajp server";
    ctx = ngx_http_get_module_ctx(r, ngx_http_ajp_module);
    rc = ngx_output_chain(&u->output, ctx->packet);
    ctx->packet = NULL;

    if (rc == NGX_ERROR) {
        // TODO: how to try next upstream
        ngx_http_upstream_finalize_request(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
        return;
    }

    if (c->write->timer_set) {
        ngx_del_timer(c->write);
    }

    if (rc == NGX_AGAIN) {
        ngx_add_timer(c->write, u->conf->send_timeout);

        if (ngx_handle_write_event(c->write, u->conf->send_lowat) != NGX_OK) {
            ngx_http_upstream_finalize_request(r, u,
                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
            return;
        }

        return;
    }

    /* rc == NGX_OK */

    if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) {
        if (ngx_tcp_push(c->fd) == NGX_ERROR) {
            ngx_log_error(NGX_LOG_CRIT, c->log, ngx_socket_errno,
                          ngx_tcp_push_n " failed");
            ngx_http_upstream_finalize_request(r, u,
                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
            return;
        }

        c->tcp_nopush = NGX_TCP_NOPUSH_UNSET;
    }

    ngx_add_timer(c->read, u->conf->read_timeout);

    /* reset handler */
    ctx = ngx_http_get_module_ctx(r, ngx_http_ajp_module);

    u->read_event_handler = ctx->read_event_handler;
    u->write_event_handler = ctx->write_event_handler;

    // TODO: process next packet
    rc = ngx_http_ajp_process_response(ctx);
    // TODO: switch rc

    if (ctx->read_ready) {
        ctx->read_ready = 0;
        c->read->ready = 1;
    }

    if (c->read->ready) {
        u->read_event_handler(r, u);
        return;
    }

    if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
        ngx_http_upstream_finalize_request(r, u,
                                           NGX_HTTP_INTERNAL_SERVER_ERROR);
        return;
    }
}

/* copy from ngx_http_upstream.c */
static void
ngx_http_upstream_finalize_request(ngx_http_request_t *r,
                                   ngx_http_upstream_t *u, ngx_int_t rc)
{
    ngx_time_t  *tp;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "finalize http upstream request: %i", rc);

    if (u->cleanup) {
        *u->cleanup = NULL;
        u->cleanup = NULL;
    }

    if (u->resolved && u->resolved->ctx) {
        ngx_resolve_name_done(u->resolved->ctx);
        u->resolved->ctx = NULL;
    }

    if (u->state && u->state->response_sec) {
        tp = ngx_timeofday();
        u->state->response_sec = tp->sec - u->state->response_sec;
        u->state->response_msec = tp->msec - u->state->response_msec;

        if (u->pipe) {
            u->state->response_length = u->pipe->read_length;
        }
    }

    u->finalize_request(r, rc);

    if (u->peer.free) {
        u->peer.free(&u->peer, u->peer.data, 0);
    }

    if (u->peer.connection) {

#if (NGX_HTTP_SSL)

        /* TODO: do not shutdown persistent connection */

        if (u->peer.connection->ssl) {

            /*
             * We send the "close notify" shutdown alert to the upstream only
             * and do not wait its "close notify" shutdown alert.
             * It is acceptable according to the TLS standard.
             */

            u->peer.connection->ssl->no_wait_shutdown = 1;

            (void) ngx_ssl_shutdown(u->peer.connection);
        }

#endif

        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "close http upstream connection: %d",
                       u->peer.connection->fd);

        if (u->peer.connection->pool) {
            ngx_destroy_pool(u->peer.connection->pool);
        }

        ngx_close_connection(u->peer.connection);
    }

    u->peer.connection = NULL;

    if (u->pipe && u->pipe->temp_file) {
        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "http upstream temp fd: %d",
                       u->pipe->temp_file->file.fd);
    }

    if (u->store && u->pipe && u->pipe->temp_file
            && u->pipe->temp_file->file.fd != NGX_INVALID_FILE) {
        if (ngx_delete_file(u->pipe->temp_file->file.name.data)
                == NGX_FILE_ERROR) {
            ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
                          ngx_delete_file_n " \"%s\" failed",
                          u->pipe->temp_file->file.name.data);
        }
    }

#if (NGX_HTTP_CACHE)

    if (r->cache) {

        if (u->cacheable) {

            if (rc == NGX_HTTP_BAD_GATEWAY || rc == NGX_HTTP_GATEWAY_TIME_OUT) {
                time_t  valid;

                valid = ngx_http_file_cache_valid(u->conf->cache_valid, rc);

                if (valid) {
                    r->cache->valid_sec = ngx_time() + valid;
                    r->cache->error = rc;
                }
            }
        }

        ngx_http_file_cache_free(r->cache, u->pipe->temp_file);
    }

#endif

    if (u->header_sent
            && rc != NGX_HTTP_REQUEST_TIME_OUT
            && (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE)) {
        rc = 0;
    }

    if (rc == NGX_DECLINED) {
        return;
    }

    r->connection->log->action = "sending to client";

    if (rc == 0
#if (NGX_HTTP_CACHE)
            && !r->cached
#endif
       ) {
        rc = ngx_http_send_special(r, NGX_HTTP_LAST);
    }

    ngx_http_finalize_request(r, rc);
}


